#ifndef STX_CORE_PRIMITIVE_TYPES_HPP
#define STX_CORE_PRIMITIVE_TYPES_HPP

/*
    This header declares some common typedefs and macro definitions for
    integer types.
    
    typedefs:
        - schar
        - uchar
        - ushort
        - uint
        - ulong
        - llong (long long or signed 64 bit integer if available)
        - ullong (unsigned long long or unsigned 64 bit integer if available)
        - int8
        - uint8
        - int16
        - uint16
        - int32
        - uint32
        - int64
        - uint64
    
    Minimum and maximum values for exact X-bit integers (X: 8, 16, 32, 64):
        - STX_EXACT_INT<X>_MIN
        - STX_EXACT_INT<X>_MAX
        - STX_EXACT_UINT<X>_MIN
        - STX_EXACT_UINT<X>_MAX
    Presense of intger type (signed and unsigned) of exact X bits:
        - STX_HAS_EXACT_INT<X>
    
    Future definitions:
        
    
    Todo:
        - Is it possible for platform to have native int64 type but not uint64 (or vice-versa)?
        - Is it guaranteed that if there is a signed type of size S than there is also corresponding
          unsigned type exactly of the same size S?
        - Is it good idea to define int8/int16/etc instead of int8_t/int16_t etc?
              Pros: this typedefs are already in stx namespace, and int8 is definitely clearer than int8_t.
              Cons: int8_t/int16_t/etc are standard conforming definitions. 
        
*/

//#define STX_HAS_STDINT_H
//#define STX_USE_STDINT_H

#if defined(STX_HAS_STDINT_H) && defined(STX_USE_STDINT_H)
#include <stdint.h>
#endif

#define STX_EXACT_INT8_MIN   (-127-1)
#define STX_EXACT_INT8_MAX   127
#define STX_EXACT_UINT8_MIN  0
#define STX_EXACT_UINT8_MAX  255
#define STX_EXACT_INT16_MIN  (-32767-1)
#define STX_EXACT_INT16_MAX  32767
#define STX_EXACT_UINT16_MIN 0U
#define STX_EXACT_UINT16_MAX 65535U
#define STX_EXACT_INT32_MIN  (-2147483647L-1)
#define STX_EXACT_INT32_MAX  2147483647L
#define STX_EXACT_UINT32_MIN 0UL
#define STX_EXACT_UINT32_MAX 4294967295UL
#define STX_EXACT_INT64_MIN  (-9223372036854775807L-1)
#define STX_EXACT_INT64_MAX  9223372036854775807L
#define STX_EXACT_UINT64_MIN 0ULL
#define STX_EXACT_UINT64_MAX 18446744073709551615ULL

#include <climits>
#include <cstddef>
#include <cfloat>

#define STX_SIZE_IN_BITS(x) (CHAR_BIT * sizeof(x))

namespace stx {

template<class T>
inline std::size_t size_in_bits(T x)
{
    return STX_SIZE_IN_BITS(x);
}

}

#define STX_BITS_IN_BYTE CHAR_BIT
#define STX_CHAR_MIN     CHAR_MIN
#define STX_CHAR_MAX     CHAR_MAX
#define STX_SCHAR_MIN    SCHAR_MIN
#define STX_SCHAR_MAX    SCHAR_MAX
#define STX_UCHAR_MIN    0
#define STX_UCHAR_MAX    UCHAR_MAX
#define STX_SHORT_MIN    SHRT_MIN
#define STX_SHORT_MAX    SHRT_MAX
#define STX_USHORT_MIN   0U
#define STX_USHORT_MAX   USHRT_MAX
#define STX_INT_MIN      INT_MIN
#define STX_INT_MAX      INT_MAX
#define STX_UINT_MIN     0U
#define STX_UINT_MAX     UINT_MAX
#define STX_LONG_MIN     LONG_MIN
#define STX_LONG_MAX     LONG_MAX
#define STX_ULONG_MIN    0UL
#define STX_ULONG_MAX    ULONG_MAX

#if CHAR_MAX < UCHAR_MAX
#define STX_CHAR_IS_SIGNED 1
#define STX_CHAR_IS_UNSIGNED 0
#else
#define STX_CHAR_IS_SIGNED 0
#define STX_CHAR_IS_UNSIGNED 1
#endif

namespace stx {

typedef signed char schar;
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;

}

#ifdef LLONG_MAX
#define STX_HAS_LONG_LONG
#define STX_LLONG_MIN LLONG_MIN
#define STX_LLONG_MAX LLONG_MAX
#define STX_ULLONG_MIN 0ULL
#define STX_ULLONG_MAX ULLONG_MAX
namespace stx {
    typedef long long llong;
    typedef unsigned long long ullong;
}
#else
//  Microsoft win32
#if defined(_WIN32) && defined(_UI64_MAX)
//#define STX_HAS_LONG_LONG
#define STX_LLONG_MIN _I64_MIN
#define STX_LLONG_MAX _I64_MAX
#define STX_ULLONG_MIN 0ULL
#define STX_ULLONG_MAX _UI64_MAX
namespace stx {
    typedef __int64 llong;
    typedef unsigned __int64 ullong;
}
#endif
#endif

#ifdef STX_LLONG_MAX
#define STX_HAS_INT64
#endif

#if ((STX_UCHAR_MAX  == STX_EXACT_UINT8_MAX) || \
     (STX_USHORT_MAX == STX_EXACT_UINT8_MAX) || \
     (STX_UINT_MAX   == STX_EXACT_UINT8_MAX) || \
     (STX_ULONG_MAX  == STX_EXACT_UINT8_MAX))
#define STX_HAS_EXACT_INT8
#endif
#if ((STX_UCHAR_MAX  == STX_EXACT_UINT16_MAX) || \
     (STX_USHORT_MAX == STX_EXACT_UINT16_MAX) || \
     (STX_UINT_MAX   == STX_EXACT_UINT16_MAX) || \
     (STX_ULONG_MAX  == STX_EXACT_UINT16_MAX))
#define STX_HAS_EXACT_INT16
#endif
#if ((STX_UCHAR_MAX  == STX_EXACT_UINT32_MAX) || \
     (STX_USHORT_MAX == STX_EXACT_UINT32_MAX) || \
     (STX_UINT_MAX   == STX_EXACT_UINT32_MAX) || \
     (STX_ULONG_MAX  == STX_EXACT_UINT32_MAX))
#define STX_HAS_EXACT_INT32
#endif
#if ((STX_UCHAR_MAX  == STX_EXACT_UINT64_MAX) || \
     (STX_USHORT_MAX == STX_EXACT_UINT64_MAX) || \
     (STX_UINT_MAX   == STX_EXACT_UINT64_MAX) || \
     (STX_ULONG_MAX  == STX_EXACT_UINT64_MAX))
#define STX_HAS_EXACT_INT64
#else
#ifdef STX_HAS_INT64
#   if (STX_ULLONG_MAX == STX_EXACT_UINT64_MAX)
#   define STX_HAS_EXACT_INT64
#   endif
#endif
#endif

#define STX_MAX(x, y) (((x) > (y)) ? (x) : (y))
#define STX_MIN(x, y) (((x) < (y)) ? (x) : (y))
#define STX_EQUAL(x, y) ((x) == (y))

#define STX_BITS_TO_DECIMAL_DIGITS(x) (3*(1 + x/10))

//  Integer C-string conversion (base 10):
//  3 bytes per every 9 bits + 1 byte for sign + 1 byte for '\0'
#define STX_INTEGER_TO_STR_MAX_SIZE(integer_type) \
    (STX_BITS_TO_DECIMAL_DIGITS(sizeof(integer_type)*CHAR_BIT) + 1 + 1)

namespace stx {

template<class integer_type>
inline int integer_to_string_size(integer_type x, bool always_print_sign = false)
{
    int printed_characters = 0;
    if (x < 0) {
        printed_characters += 1;
        x *= -1;
    } else if (always_print_sign) {
        printed_characters += 1;
    }
    integer_type base = 10, q, r;
    while (1) {
        q = x/base;
        r = x%base;
        if (q == 0 && r == 0) {
            break;
        }
        x = q;
        printed_characters += 1;
    }
    return printed_characters;
}

//  Destination string has minimum size of
//  STX_INTEGER_TO_STR_MAX_SIZE(integer_type) bytes.
//  Returns number of printed characters excluding '\0'.
template<class integer_type>
inline int integer_to_string(integer_type x, char* s, bool always_print_sign = false)
{
    if (!s) {
        return integer_to_string_size(x, always_print_sign);
    }
    int printed_characters = 0;
    char digit_to_character[] = "0123456789";
    if (x < 0) {
        *s = '-';
        ++s;
        ++printed_characters;
        x *= -1;
    } else if (always_print_sign) {
        *s = '+';
        ++s;
        ++printed_characters;
    }
    integer_type base = 10, q, r;
    char* p = s;
    std::size_t size = 0;
    while (1) {
        q = x/base;
        r = x%base;
        if (q == 0 && r == 0) {
            break;
        }
        size += 1;
        x = q;
        *s = digit_to_character[r];
        ++s;
        ++printed_characters;
    }
    *s = '\0';
    --s;
    char tmp;
    for (size /= 2; size != 0; --size) {
        tmp = *s;
        *s = *p;
        *p = tmp;
        --s;
        ++p;
    }
    return printed_characters;
}

} // namespace stx

namespace stx {

#ifndef STX_USE_STDINT_H

typedef schar int8;
typedef uchar uint8;

#if ((STX_UCHAR_MAX >= STX_EXACT_UINT16_MAX) && (STX_UCHAR_MAX < STX_USHORT_MAX))
typedef schar int16;
typedef uchar uint16;
#elif STX_USHORT_MAX >= STX_EXACT_UINT16_MAX
typedef short int16;
typedef ushort uint16;
#elif STX_UINT_MAX >= STX_EXACT_UINT16_MAX
typedef int int16;
typedef uint uint16;
#elif STX_ULONG_MAX >= STX_EXACT_UINT16_MAX
typedef long int16;
typedef ulong uint16;
#endif

#if ((STX_UCHAR_MAX >= STX_EXACT_UINT32_MAX) && (STX_UCHAR_MAX < STX_USHORT_MAX))
typedef schar int32;
typedef uchar uint32;
#elif STX_USHORT_MAX >= STX_EXACT_UINT32_MAX
typedef short int32;
typedef ushort uint32;
#elif STX_UINT_MAX >= STX_EXACT_UINT32_MAX
typedef int int32;
typedef uint uint32;
#elif STX_ULONG_MAX >= STX_EXACT_UINT32_MAX
typedef long int32;
typedef ulong uint32;
#endif

#ifdef STX_HAS_INT64
typedef llong int64;
typedef ullong uint64;
#endif

#else // STX_USE_STDINT_H

typedef int8_t   int8;
typedef uint8_t  uint8;
typedef int16_t  int16;
typedef uint16_t uint16;
typedef int32_t  int32;
typedef uint32_t uint32;
typedef int64_t  int64;
typedef uint64_t uint64;

#endif // STX_USE_STDINT_H

//  Size of float-to-string in decimal form.
//  Number of digits of max exponent in decimal form:
//  IEEE-754
//  binary16: 5
//  binary32: 39
//  binary64: 308
//  binary128: 4932
//  decimal32: 96
//  decimal64: 384
//  decimal128: 6144
//  1 byte for sign
//  1 bite for '.'
//  3 bytes per every 9 bits of mantissa
#define STX_FLOAT_TO_STR_DECIMAL_MAX_SIZE \
    (6144 + 1 + 1 + STX_BITS_TO_DECIMAL_DIGITS(FLT_MANT_DIG))

//  Size of float-to-string in scientific form:
//  1 byte for significand sign
//  3 bytes per every 9 bits of mantissa
//  1 byte for '.'
//  1 byte for 'e'
//  1 byte for exponent sign
//  3 bytes per every 9 bits of mantissa
//  1 byte for '\0'
#define STX_FLOAT_TO_STR_SCIENTIFIC_MAX_SIZE \
    (1 + STX_BITS_TO_DECIMAL_DIGITS(FLT_MANT_DIG) + 1 + 1 + 1 + \
     STX_BITS_TO_DECIMAL_DIGITS(STX_SIZE_IN_BITS(float) - FLT_MANT_DIG) + 1)

#define STX_FLOAT_TO_STR_MAX_SIZE \
    STX_MAX(STX_FLOAT_TO_STR_DECIMAL_MAX_SIZE, STX_FLOAT_TO_STR_SCIENTIFIC_MAX_SIZE)

inline int float_to_string(float x, char* s, bool scientific_format)
{
    int size = 0;
    
    return size;
}

} // namespace stx

#endif // STX_CORE_PRIMITIVE_TYPES_HPP
